[FEAT] Suspense/ErrorBoundary 공통 래퍼(AsyncBoundary) 추가#115
Conversation
useSearchParams 등 클라이언트 훅 사용 시 필요한 Suspense 경계와, 향후 비동기 데이터 페칭 시 필요한 에러 처리를 공통 AsyncBoundary 컴포넌트로 추가했습니다.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthrough
ChangesAsyncBoundary/ErrorBoundary 공통 래퍼
Estimated code review effort: 1 (Trivial) | ~5 minutes Sequence Diagram(s)sequenceDiagram
participant Parent
participant AsyncBoundary
participant ErrorBoundary
participant Suspense
Parent->>AsyncBoundary: children, pendingFallback, errorFallback 전달
AsyncBoundary->>Suspense: children을 pendingFallback과 함께 래핑
alt errorFallback 존재
AsyncBoundary->>ErrorBoundary: Suspense 결과를 errorFallback과 함께 래핑
ErrorBoundary-->>Parent: fallback 또는 children 렌더링
else errorFallback 없음
Suspense-->>Parent: Suspense 결과만 렌더링
end
관련 이슈: 제안 리뷰어: 짧고 명료한 두 파일이니, boundary 관련 코드를 자주 다루시는 분이 좋겠습니다. 시 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Timo Performance ReportBundle Size — timo-web
Lighthouse — timo-web
Image Optimization — timo-web
측정 커밋: |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/timo-web/components/boundary/AsyncBoundary.tsx`:
- Line 8: The errorFallback type in AsyncBoundary is duplicated from
ErrorBoundary’s ErrorFallback type, so export ErrorFallback from
ErrorBoundary.tsx and reuse that shared type in AsyncBoundary instead of
redefining the union. Update the props/type definitions around AsyncBoundary and
ErrorBoundary so both reference the same exported symbol, keeping the naming
rule intact while preventing the two files from drifting apart.
In `@apps/timo-web/components/boundary/ErrorBoundary.tsx`:
- Around line 20-46: `ErrorBoundary` only uses `getDerivedStateFromError` to
update UI state, but it does not report caught errors to monitoring tools. Add
`componentDidCatch` to the `ErrorBoundary` class and route the received `error`
and `errorInfo` to your logging/telemetry integration (for example Sentry),
while keeping the existing `reset` and `fallback` behavior unchanged.
- Around line 7-9: `ErrorFallback` is a union type alias, so rename it to follow
the type alias convention by adding the required `Types` suffix. Update the
`ErrorBoundary.tsx` type alias declaration and any references that use
`ErrorFallback` so the naming is consistent with the project rule for
union/tuple/literal aliases.
- Around line 16-18: Update ErrorBoundary to handle non-Error throw values
safely: in ErrorBoundary and its getDerivedStateFromError flow, stop assuming
the captured value is always an Error and accept unknown instead. Normalize the
caught value before storing or passing it to the fallback, using an instanceof
Error check to convert strings/objects into a safe Error shape so
ErrorBoundaryState and the fallback rendering stay robust.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 4d57e387-8e6a-4d79-86b5-d69c505e3895
📒 Files selected for processing (2)
apps/timo-web/components/boundary/AsyncBoundary.tsxapps/timo-web/components/boundary/ErrorBoundary.tsx
- ErrorFallbackTypes를 ErrorBoundary에서 export하여 AsyncBoundary가 재사용하도록 했습니다 - getDerivedStateFromError가 unknown 값을 안전하게 Error로 정규화하도록 수정했습니다 - componentDidCatch를 추가해 캐치된 에러를 Sentry로 전송하도록 했습니다
jjangminii
left a comment
There was a problem hiding this comment.
PR 설명 자체가 "Suspense/ErrorBoundary 추가" + "리뷰에서 지적된 타입 중복·에러 처리 문제 수정"을 함께 담고 있어요. 타입 중복 제거(ErrorFallbackTypes export/재사용)와 unknown 에러 정규화(toError)를 리뷰 사이클 안에서 바로 해결하신 흐름이 깔끔했습니다.
캐치된 에러가 fallback UI로만 처리되고 어디에도 리포팅되지 않으면 실제 장애를 놓칠 수 있다는 문제를 인식하고, 기존 global-error.tsx와 동일한 Sentry 패턴을 그대로 재사용해 일관성 있게 연결하신 점이 좋아요. 새로운 리포팅 방식을 만들지 않고 기존 컨벤션을 따른 판단도 적절했습니다.
저도 Next.js 환경에서 바운더리 다루는 건 이번에 처음이라 많이 배워갑니다~
yumin-kim2
left a comment
There was a problem hiding this comment.
로딩 중이랑 에러 났을 때 화면을 매번 따로 만들지 않게, 하나의 재사용 부품(AsyncBoundary)으로 표준화한 점이 좋은 것 같습니다.👍 PR 보면서 덕분에 Suspense/ErrorBoundary에 대해 알 수 있었고 몰랐던 부분에 대해 공부도 할 수 있었씁니다 수고하셨어요!!!
ISSUE 🔗
close #114
What is this PR? 🔍
Suspense/ErrorBoundary를 조합한 공통
AsyncBoundary를 추가하고, 이후 리뷰에서 지적된 타입 중복·에러 처리 안전성 문제를 함께 수정했습니다.배경
<Suspense>를 작성하거나, 에러 처리는 아예 하지 않는 구조였습니다.useSearchParams()처럼 Next.js가 Suspense 경계를 요구하는 클라이언트 훅을 쓸 때마다 반복 작업이 필요했고, 향후 실제 비동기 데이터 페칭(React Query suspense 모드 등)을 붙일 때 로딩/에러 처리가 표준화되어 있지 않았습니다.AsyncBoundary컴포넌트를 만들어, 에러 처리가 필요 없는 경우와 필요한 경우를 하나의 컴포넌트로 함께 지원하도록 했습니다.언제 · 왜 사용하는가
useSearchParams()등 Next.js가 Suspense 경계를 강제하는 클라이언트 훅을 쓰는 컴포넌트를 감쌀 때errorFallback없이<AsyncBoundary pendingFallback={...}>로 감싸면 됩니다. 이 경우 내부적으로<Suspense>만 적용되고ErrorBoundary는 렌더링에 추가되지 않습니다.errorFallback을 함께 넘겨야 합니다.errorFallback이 있으면<Suspense>바깥을<ErrorBoundary>로 한 번 더 감싸, 로딩 중 실패와 렌더링 중 예외를 모두 fallback UI로 처리하고reset()으로 재시도할 수 있게 합니다.AsyncBoundary하나로 표준화해 이런 누락을 막습니다.boundary 컴포넌트 추가
components/boundary/ErrorBoundary.tsx(클래스 컴포넌트)와components/boundary/AsyncBoundary.tsx(Suspense + optional ErrorBoundary)를 추가했습니다.getDerivedStateFromError를 쓰는 클래스 컴포넌트가 필요했고, Suspense 전용 래퍼와 Suspense+에러처리 래퍼를 별도 컴포넌트로 나눌지 검토했으나 이름이 비슷해 어떤 걸 써야 할지 헷갈릴 수 있어 하나로 통합했습니다.AsyncBoundary는errorFallbackprop을 선택적으로 받습니다.errorFallback을 넘기지 않으면<Suspense fallback={pendingFallback}>만으로 감싸고, 넘기면 그 바깥을<ErrorBoundary fallback={errorFallback}>로 한 번 더 감쌉니다.리뷰 대응: 타입 공유 및 에러 처리 안전성 개선
AsyncBoundary가ErrorBoundary의 fallback 타입을 중복 정의하던 것을export된 공용 타입으로 재사용하도록 바꾸고,ErrorBoundary가Error가 아닌 값이 throw되는 경우와 에러 모니터링 누락 문제를 함께 처리했습니다.AsyncBoundary와ErrorBoundary가 각자 동일한 유니온 타입을 따로 선언하고 있어 한쪽만 바뀌면 타입이 어긋날 위험이 있었습니다. 또한getDerivedStateFromError는 React 타입상Error만 받는 것처럼 보이지만 실제로는 문자열이나 객체가 그대로 throw될 수 있어.message등에 접근하면 런타임 에러로 이어질 수 있었고, 캐치된 에러가 화면 fallback으로만 처리되고 어디에도 리포팅되지 않아 실제 장애를 놓칠 수 있었습니다.ErrorBoundary.tsx에서ErrorFallbackTypes를export하고AsyncBoundary.tsx는 이를 그대로 import해 씁니다.getDerivedStateFromError(error: unknown)과componentDidCatch(error: unknown, errorInfo)가 공통toError()헬퍼로 값을Error인스턴스로 정규화한 뒤,componentDidCatch에서Sentry.captureException(toError(error), { contexts: { react: { componentStack } } })로 리포팅합니다. 기존reset()/fallback렌더링 동작은 그대로 유지했습니다.@sentry/nextjs(app/global-error.tsx에서 쓰는 것과 동일한 패턴)를 그대로 사용했습니다.To Reviewers
errorFallback을 선택적으로 두고 유무에 따라ErrorBoundary래핑 여부를 분기한 설계가 적절한지, 그리고componentDidCatch에서의 Sentry 리포팅 방식이global-error.tsx의 기존 패턴과 일관되는지 봐주세요. 아직 실제 사용처에 연결하지 않은 상태라 이번 PR만으로는 동작을 눈으로 확인할 수 있는 화면은 없습니다.Screenshot 📷
Test Checklist ✔
pnpm --filter timo-web check-types통과pnpm --filter timo-web lint통과